'use strict'

//! 切换工作目录
process.chdir(__dirname)

const titbit=require('titbit');
const fs=require('fs');
const dbcfg=require('./dbconfig.js');
const pg=require('pg');

const wxkey=require('./wxkey.js');
const {httpcli}=require('gohttp');//!封装了http模块的一个库
const token=require('titbit-token');

//todo 通过try catch保证images文件夹存在 
try{
    //! 是否存在images目录 
    fs.accessSync('./images');
}catch(err){
    //! 没有的话创建目录 
    fs.mkdirSync('./images');
}
//! Pool方法连接池，连接数据库 
let psql=new pg.Pool(dbcfg);


//todo 添加域名证书 
let cert_path='/usr/local/share';
const app=new titbit({
    // 开启调试模式，若有错误会出现错误信息
    debug:true,

    globalLog:true,//!开启全局日志

    cert:`${cert_path}/api.hiehie.top.pem`,
    key:`${cert_path}/api.hiehie.top.key`
})

//添加路由
// get请求 根路径  必须async声明  c请求上下文
app.get('/',async c=>{
    c.send('OK');
})

/** ------------content路由----------------- */
//! 获取内容列表
app.get('/content',async c=>{
    let ret=await psql.query('select * from content');
    //! 等效于c.res.body=ret.rows 
    c.send(ret.rows);
})
//!获取具体内容 
app.get('/content/:id',async c=>{
    let sqltext=`select * from content WHERE id=$1`
    let r=await psql.query(sqltext,[c.param.id]);
    //! rowCount代表获取多少行，没有代表获取id值不对，404
    if(r.rowCount===0){
        return c.status(404).send('content not found')
    }
    //! rows数组 把第一个元素传上去???
    c.send(r.rows[0])
})
//! 创建内容
        //! 路由     对路由进行处理的回调函数
app.post('/content',async c=>{
    try{
        //! 提交Body数据，通过c.body获取 
        let data=JSON.parse(c.body);
        //! text/*      c.body 就是string类型
        //! multipart   c.body={} c.files={} 对象类型{}
        //! 其他类型     c.body c.rawBody(buffer类型)
        /**
         *todo HTTP 消息头 content-type指定了数据的类型（数据格式）
         * 
         *todo Client                                             Sever
         *todo   content-type描述的值要和提交的Body数据一致            根据Content-type的值决定如何解析Body数据
         * 
         *todo   content-type:
         *todo      multipart/form-data;boundary=xxx-----------------文件解析
         *todo      text/*                          -----------------文本类型
         *todo      application/*                   -----------------要看具体值，application/json也是文本类型
         *todo      .......                         -----------------每种类型如何处理双方设计好的通信方式
         * 
         *todo   到web框架这一层面，则如何处理开发框架设计好的，但通常都要遵循一个原则:
         *todo          允许开发者做自己的处理
         * 
         *todo   在本框架范围内：
         *todo      multipart类型, c.body是object类型，文件信息解析到c.files
         *todo      application/x-www-form-urlencoded 传统的表单类型， c.body是object类型，用于获取提交的表单数据
         *todo      text/*, c.body是文本类型，c.body=c.rawBody.toString('utf-8);
         *todo      其它类型，c.body是buffer类型，就是对c.rawBody的引用
         */
        let sqltext=`INSERT INTO content(id, add_time, detail, title, update_time)`+` Values($1, $2, $3, $4, $5)`;
        //! 生成唯一id 
        let id=c.helper.makeId();
        let tm=Date.now();

        let r=await psql.query(sqltext,[id, tm, data.detail, data.title, tm]);

        if(r.rowCount===0){
            return c.status(500).send('failed');
        }

        c.send(id);

    }catch(err){
        c.status(400).send('it is not json data');
    }
})

//!删除
app.delete('/content/:id',async c=>{
    let sqltext=`delete from content WHERE id=$1`
    let r=await psql.query(sqltext,[c.param.id]);
    c.send(r);
})
/** ------------content路由——END--------------- */
//? 读取html页面 
app.get('/upload',async c=>{
    //! c.reply 是响应对象，可写的流
    //! c.reply 在http/1.1协议里面指向response
    //! c.reply 在http/2协议里面指向http2 stream
    await c.helper.pipe('./upload.html',c.reply);
})
//? upload.html 发出post请求，捕获到post请求进行相应处理 
app.post('/upload',async c=>{
    //! 根据name获取文件对象
    let f=c.getFile('image');//f获取到的属性 filename  content-type  start  end  length
    //! 没有找到文件对象时候，f返回null，在此判断是否成功找到 
    if(!f){
        return c.status(400).send('image not found');
    }

    //! 根据传入的原始文件名字生成唯一文件名，主要使用原始文件f.filename的扩展名
    let fname=c.helper.makeName(f.filename);
    //! 移动文件到指定位置
    await c.moveFile(f,`./images/${fname}`)

    c.send(fname);
})
//! 返回图片数据  冒号表示这个路由后面这 'name' 是动态变化的
app.get('/image/:name',async c=>{//!冒号后面为参数，动态变化 类似于./img/a.jpg
    //! c.param是一个对象，其中的key值是路由参数的名称
    let imgname=c.param.name;

    await c.helper.pipe(`./images/${imgname}`,c.reply)
})

let tok=new token({
    // token有效期，单位为秒，45秒超时
    expires:45,
    //必须是32位字符  key是加密的值，加密的密钥，面向开发者的密码
    key:'123a56789123f56789w234r678912345',
    // 必须16位字符 用作初始干扰的数据
    iv:'1g345r7812g45678'
})

app.get('/mp-login/:code',async c=>{
    // c.send(c.param);
    let auth_url=`https://api.weixin.qq.com/sns/jscode2session`+
                `?appid=${wxkey.appid}`+
                `&secret=${wxkey.secret}`+
                `&js_code=${c.param.code}`+
                `&grant_type=authorization_code`
    //! 对微信服务器发起一个get请求，请求成功之后，返回openid（用户唯一标识）
    //!开放密码 session_key，unionid，errcode，errmsg等 
    let ret=await httpcli.get(auth_url)
                        .then(res=>{
                            return res.json() // blob() json() text()格式都可以
                        })
    // c.send(ret)
    c.send(tok.make(ret))//tok.make进行加密
})

// 验证的路由
app.get('/decrypt/:token',async c=>{
    c.send(tok.verify(c.param.token));
})

// 运行在1234端口
app.run(2345);